/**
 * @file HLSRecord.cpp
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 0.1
 * @date 2020-09-21
 * 
 * @copyright Copyright Sci-Tech Laboratories (c) 2020
 * 
 */

#include "HLSRecord.h"

#ifdef LYL_DEBUG
#include "vscode.h"
#endif //LYL_DEBUG

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libavutil/avstring.h>
#include <libavutil/timestamp.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/packet.h>
#include <libavutil/mem.h>
#include <libavutil/mathematics.h>
#ifdef __cplusplus
};
#endif

#include "STL_FS/STL_FileSystem.h"
#include "STL_Timestamp/STL_Timestamp.h"
#include "STL_Path/STL_Path.h"

struct options_t
{
    const char *input_file;
    const char *output_prefix;
    double segment_max_duration;
    char *m3u8_file;
    char *tmp_m3u8_file;
    const char *url_prefix;
    unsigned int write_m3u8;
    unsigned int write_index;
    unsigned int sequence;
};

int write_index_header(FILE *index_fp, const struct options_t options)
{
    char *write_buf;
    write_buf = (char *)malloc(sizeof(char) * 1024);
    if (!write_buf)
    {
        fprintf(stderr, "Could not allocate write buffer for index file, index file will be invalid\n");
        return -1;
    }

    snprintf(write_buf, 1024, "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:%-5lu\n#EXT-X-MEDIA-SEQUENCE:%d\n", (long)options.segment_max_duration, options.sequence);
    if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
    {
        fprintf(stderr, "Could not write to m3u8 index file, will not continue writing to index file\n");
        free(write_buf);
        return -1;
    }
    free(write_buf);
    return 0;
}

int write_index_segment(FILE *index_fp, const struct options_t options, const char *basename, unsigned int output_index, double duration)
{
    char *write_buf;
    write_buf = (char *)malloc(sizeof(char) * 1024);
    if (!write_buf)
    {
        fprintf(stderr, "Could not allocate write buffer for index file, index file will be invalid\n");
        return -1;
    }
    snprintf(write_buf, 1024, "#EXTINF:%f,\n%s%s-%u.ts\n", duration, options.url_prefix, basename, output_index);
    if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
    {
        fprintf(stderr, "Could not write to m3u8 index file, will not continue writing to index file\n");
        free(write_buf);
        return -1;
    }
    free(write_buf);
    return 0;
}

int write_index_trailer(FILE *index_fp)
{
    char *write_buf;
    write_buf = (char *)malloc(sizeof(char) * 1024);
    if (!write_buf)
    {
        fprintf(stderr, "Could not allocate write buffer for index file, index file will be invalid\n");
        return -1;
    }
    snprintf(write_buf, 1024, "#EXT-X-ENDLIST\n");
    if (fwrite(write_buf, strlen(write_buf), 1, index_fp) != 1)
    {
        fprintf(stderr, "Could not write last file and endlist tag to m3u8 index file\n");
        free(write_buf);
        return -1;
    }

    free(write_buf);
    return 0;
}

namespace GBXUEAN
{

    void HLSRecord::log_packet(const AVFormatContext *fmt_ctx, AVPacket *pkt, const char *tag)
    {
        AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

        printf("%s: ", tag);
        printf("pts:%lld ", pkt->pts);
        printf("pts_time:%f ", pkt->pts * 1.f / time_base->den);
        printf("dts:%lld ", pkt->dts);
        printf("dts_time:%f ", pkt->dts * 1.f / time_base->den);
        printf("duration:%lld ", pkt->duration);
        printf("duration_time:%f ", pkt->duration * 1.f / time_base->den);
        printf("stream_index:%d ", pkt->stream_index);
        printf("pos:%lld ", pkt->pos);
        printf("size:%d\n", pkt->size);
    }

    void HLSRecord::configurationinfo()
    {
        printf("Configuration Information:\n");
        printf("%s\n", avcodec_configuration());
        // av_log_set_level(AV_LOG_ERROR);//设置日志级别 可用
    }

    HLSRecord::HLSRecord(HLSRecord::Option option)
        : m_isWork(false), m_ofmtCtx(nullptr), m_output_packet_index(0), m_start_pts(0), m_start_dts(0), m_streamId(option.streamId), m_prefix(option.prefix), m_duration(option.duration), m_sequence(option.sequence), m_audio_input_stream_index(-1), m_video_input_stream_index(-1), m_audio_output_stream_index(-1), m_video_output_stream_index(-1), m_currentPath(""), m_currentHour(""), m_currentDateName(""), m_currentDateTimeName(""), m_currentMpegTSFilePath(""), m_currentSequenceName("")
    {
        m_streamName = std::string("stream_") + std::to_string(m_streamId);
        m_livePath = m_currentPath = STL::STL_Path::join(m_prefix, m_streamName);
        std::string liveM3u8FileName = m_streamName + "_live.m3u8";
        m_liveM3u8FilePath = STL::STL_Path::join(m_livePath, liveM3u8FileName);
    }

    int HLSRecord::start(const AVFormatContext *ifmtCtx)
    {
        if (m_isWork)
        {
            fprintf(stderr, "Error: HLSRecord is Work\n");
            return -1;
        }

        int ret = 0;

        AVOutputFormat *ofmt = av_guess_format("mpegts", NULL, NULL);
        if (!ofmt)
        {
            fprintf(stderr, "Could not find MPEG-TS muxer\n");
            return -1;
        }
        m_ofmtCtx = avformat_alloc_context();
        if (!m_ofmtCtx)
        {
            fprintf(stderr, "Could not allocated output context");
            return -1;
        }
        m_ofmtCtx->oformat = ofmt;

        int stream_index = 0;
        for (int i = 0; i < ifmtCtx->nb_streams; i++)
        {
            AVStream *out_stream;
            AVStream *in_stream = ifmtCtx->streams[i];
            AVCodecParameters *in_codecpar = in_stream->codecpar;
            if (in_codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
            {
                return 0; // 暂时不处理音频
                if (m_audio_input_stream_index != -1 ||
                    m_audio_output_stream_index != -1)
                {
                    break; // 只处理一路音频
                }
                m_audio_input_stream_index = in_stream->index;
                m_audio_output_stream_index = stream_index++;
                m_audio_time_base = in_stream->time_base;
            }
            else if (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                if (m_video_input_stream_index != -1 ||
                    m_video_output_stream_index != -1)
                {
                    break; // 只处理一路视频
                }
                m_video_input_stream_index = in_stream->index;
                m_video_output_stream_index = stream_index++;
                m_video_time_base = in_stream->time_base;
            }
            else
            {
                printf("other stream type :%d", in_codecpar->codec_type);
                continue;
            }

            out_stream = avformat_new_stream(m_ofmtCtx, NULL);
            if (!out_stream)
            {
                fprintf(stderr, "Failed allocating output stream\n");
                // ret = AVERROR_UNKNOWN;
                return -1;
            }
            ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
            if (ret < 0)
            {
                fprintf(stderr, "Failed to copy codec parameters\n");
                return -1;
            }
            out_stream->codecpar->codec_tag = 0;
        }
        std::string outputInfo = std::string("output Stream ID：") + std::to_string(m_streamId);
        printf("------------- ontput Information -------------\n");
        av_dump_format(m_ofmtCtx, 0, outputInfo.c_str(), 1);
        printf("----------------------------------------------\n");
    }
    void HLSRecord::stop()
    {
        //TODO 清理数据
        m_isWork = false;
    }

    std::string HLSRecord::getNextPath()
    {
        m_currentSequenceName = std::to_string(m_sequence++);
        std::string datetime = STL::getTimestampString("%Y%m%d%H%M%S");
        m_currentFileName = m_streamName + "-" + datetime + "-" + m_currentSequenceName + ".ts";
        std::string currentHour = STL::getTimestampString("%Y%m%d%H");
        if (m_currentHour != currentHour)
        {
            m_currentHour = currentHour;
            m_recordList.clear();
            m_currentDateName = STL::getTimestampString("%Y%m%d");
            m_currentDateTimeName = datetime;
            std::vector<std::string> paths;
            paths.push_back(m_prefix);
            paths.push_back(m_streamName);
            paths.push_back(m_currentDateName);
            paths.push_back(m_currentDateTimeName);
            m_currentPath = STL::STL_Path::join(paths);
            STL::makeDirWithParents(m_currentPath);
            m_currentM3u8FilePath = STL::STL_Path::join(m_currentPath, (m_streamName + "_record.m3u8"));
        }
        m_currentMpegTSFilePath = STL::STL_Path::join(m_currentPath, m_currentFileName);
        // m_currentLiveFileName = std::string("./") + m_currentDateName + "/" + m_currentDateTimeName + "/" + m_currentFileName; //TODO 工具处理../. 改为工具函数处理
        m_currentLiveFileName = m_currentDateName + "/" + m_currentDateTimeName + "/" + m_currentFileName; //TODO 工具处理../. 改为工具函数处理
        return m_currentMpegTSFilePath;
    }

    int HLSRecord::onPacket(AVPacket *packet)
    {
        bool isVideo = false;
        bool isAudio = false;
        int ret = 0;

        AVStream *out_stream = nullptr;
        double tmp_segment_time, segment_time;
        recordPacketInfo(packet);
        if (packet->stream_index == m_video_input_stream_index)
        {
            isVideo = true;
            packet->stream_index = m_video_output_stream_index;
            for (size_t i = 0; i < m_ofmtCtx->nb_streams; i++)
            {
                if (m_ofmtCtx->streams[i]->index == m_video_output_stream_index)
                {
                    out_stream = m_ofmtCtx->streams[i];
                }
            }
        }
        else if (packet->stream_index == m_audio_input_stream_index)
        {
            return 0; // TODO 暂时不处理音频
            isAudio = true;
            packet->stream_index = m_audio_output_stream_index;
            for (size_t i = 0; i < m_ofmtCtx->nb_streams; i++)
            {
                if (m_ofmtCtx->streams[i]->index == m_audio_output_stream_index)
                {
                    out_stream = m_ofmtCtx->streams[i];
                }
            }
        }
        else
        {
            return 0;
        }
        if (!out_stream)
        {
            fprintf(stderr, "Error: out_stream error\n");
            return -1;
        }
        if (isVideo && (packet->flags & AV_PKT_FLAG_KEY))
        {
            double currentDuration = (packet->pts - m_start_pts) * av_q2d(m_video_time_base);
            if (m_output_packet_index > 0 && currentDuration > m_duration)
            {
                av_write_trailer(m_ofmtCtx);
                if (ret < 0)
                {
                    fprintf(stderr, "Error occurred when opening output file\n");
                    return -1;
                }
                avio_flush(m_ofmtCtx->pb);
                avio_closep(&m_ofmtCtx->pb);
                // out_stream->cur_dts = AV_NOPTS_VALUE; // 必须设置，否则错误
                m_output_packet_index = 0;

                HLSRecord::RecordInfo info;
                info.sequenceName = m_currentSequenceName;
                info.liveRelativePath = m_currentLiveFileName;
                info.recordRelativePath = m_currentFileName;
                info.duration = currentDuration;
                m_recordList.push_back(info);
                if (m_liveList.size() != 0)
                {
                    HLSRecord::RecordInfo tempInfo = m_liveList.at(m_liveList.size() - 1);
                    m_liveList.clear();
                    m_liveList.push_back(tempInfo);
                }
                m_liveList.push_back(info);
                writeM3u8();
            }
            if (m_output_packet_index == 0)
            {
                ret = avio_open(&m_ofmtCtx->pb, getNextPath().c_str(), AVIO_FLAG_WRITE);
                if (ret < 0)
                {
                    fprintf(stderr, "Error occurred when opening output file\n");
                    return -1;
                }
                ret = avformat_write_header(m_ofmtCtx, NULL);
                if (ret < 0)
                {
                    fprintf(stderr, "Error occurred when opening output file\n");
                    return -1;
                }
                m_start_pts = packet->pts;
                m_start_dts = packet->dts;
                //TODO 记录音频开始时间戳
            }
            m_output_packet_index++;
            printf("output_index:%d\n", m_output_packet_index);
        }
        if (m_output_packet_index != 0) // 等关键帧
        {
            /* copy packet */
            packet->pts = av_rescale_q_rnd(packet->pts, m_video_time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            packet->dts = av_rescale_q_rnd(packet->dts, m_video_time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            // packet->pts = av_rescale_q_rnd(packet->pts - m_start_pts, m_video_time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            // packet->dts = av_rescale_q_rnd(packet->dts - m_start_dts, m_video_time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            packet->duration = av_rescale_q(packet->duration, m_video_time_base, out_stream->time_base);
            packet->pos = -1;
            //TODO 修正音频帧时间戳

            log_packet(m_ofmtCtx, packet, "out");

            ret = av_interleaved_write_frame(m_ofmtCtx, packet); // 缓存排序
            // ret = av_write_frame(m_ofmtCtx, packet);//不缓存排序

            if (ret < 0)
            {
                fprintf(stderr, "Error muxing packet\n");
            }
        }
        recoveryPacketInfo(packet);
    }

    int HLSRecord::writeM3u8()
    {
        return (wirteLiveM3u8() + wirteRecordM3u8());
    }
    int HLSRecord::wirteLiveM3u8()
    {
        std::string data = "";
        double max_duration = 0;
        std::string startSeq = "";
        for (size_t i = 0; i < m_liveList.size(); i++)
        {
            if (startSeq.empty())
                startSeq = m_liveList.at(i).sequenceName;
            max_duration = (max_duration > m_liveList.at(i).duration) ? max_duration : m_liveList.at(i).duration;
        }

        char *write_buf;
        write_buf = (char *)malloc(sizeof(char) * 1024);
        if (!write_buf)
        {
            fprintf(stderr, "Could not allocate write buffer for index file, index file will be invalid\n");
            return -1;
        }

        snprintf(write_buf, 1024, "#EXTM3U\r\n#EXT-X-VERSION:3\r\n#EXT-X-TARGETDURATION:%-5lu\r\n#EXT-X-MEDIA-SEQUENCE:%s\r\n", (long)max_duration, startSeq.c_str());

        data.append(std::string(write_buf));

        for (size_t i = 0; i < m_liveList.size(); i++)
        {
            snprintf(write_buf, 1024, "#EXTINF:%f,\r\n%s\r\n", m_liveList.at(i).duration, m_liveList.at(i).liveRelativePath.c_str());
            data.append(std::string(write_buf));
        }
        free(write_buf);
        // data.append("#EXT-X-ENDLIST\r\n");
        STL::writeStringToFile(data, m_liveM3u8FilePath);
    }
    int HLSRecord::wirteRecordM3u8()
    {
        std::string data = "";
        double max_duration = 0;
        std::string startSeq = "";
        for (size_t i = 0; i < m_recordList.size(); i++)
        {
            if (startSeq.empty())
                startSeq = m_recordList.at(i).sequenceName;
            max_duration = (max_duration > m_recordList.at(i).duration) ? max_duration : m_recordList.at(i).duration;
        }

        char *write_buf;
        write_buf = (char *)malloc(sizeof(char) * 1024);
        if (!write_buf)
        {
            fprintf(stderr, "Could not allocate write buffer for index file, index file will be invalid\n");
            return -1;
        }

        snprintf(write_buf, 1024, "#EXTM3U\r\n#EXT-X-VERSION:3\r\n#EXT-X-TARGETDURATION:%-5lu\r\n#EXT-X-MEDIA-SEQUENCE:%s\r\n", (long)max_duration, startSeq.c_str());

        data.append(std::string(write_buf));

        for (size_t i = 0; i < m_recordList.size(); i++)
        {
            snprintf(write_buf, 1024, "#EXTINF:%f,\r\n%s\r\n", m_recordList.at(i).duration, m_recordList.at(i).recordRelativePath.c_str());
            data.append(std::string(write_buf));
        }
        free(write_buf);
        data.append("#EXT-X-ENDLIST\r\n");
        STL::writeStringToFile(data, m_currentM3u8FilePath);
    }
} // namespace GBXUEAN
